package com.nulldev.util.scripting.ScriptAPI.brainfuck;

import java.nio.ByteBuffer;

public class BrainfuckInterpreter {
	public final int maxCycleCount;
	public final int memory;

	public BrainfuckInterpreter(int maxCycleCount, int memory) {
		this.maxCycleCount = maxCycleCount;
		this.memory = memory;
	}

	public String process(final char[] code, final String input) {
		final ByteBuffer bytes = ByteBuffer.allocateDirect(this.memory);
		int data = 0;
		final char[] inChars = input.toCharArray();
		int inChar = 0;
		final StringBuilder output = new StringBuilder(128);
		int cycleCount = 0;
		try {
			for (int instruction = 0; instruction < code.length; ++instruction) {
				cycleCount++;
				if (cycleCount > maxCycleCount) {
					throw new BrainfuckException("Exceeded max amount of cycles (" + maxCycleCount + ")");
				}
				char command = code[instruction];
				switch (command) {
					case '>':
						++data;
						break;
					case '<':
						--data;
						if (data < 0) {
							throw new BrainfuckException("Data pointer out of bounds");
						}
						break;
					case '+':
						bytes.put(data, (byte) (bytes.get(data) + 1));
						break;
					case '-':
						bytes.put(data, (byte) (bytes.get(data) - 1));
						break;
					case '.':
						output.append((char) bytes.get(data));
						break;
					case ',':
						try {
							bytes.put(data, (byte) inChars[inChar++]);
							break;
						} catch (IndexOutOfBoundsException ex) {
							throw new BrainfuckException("Input out of bounds");
						}
					case '[':
						if (bytes.get(data) == 0) {
							int depth = 1;
							do {
								command = code[++instruction];
								if (command == '[') {
									++depth;
								} else if (command == ']') {
									--depth;
								}
							} while (depth > 0);
						}
						break;
					case ']':
						if (bytes.get(data) != 0) {
							int depth = -1;
							do {
								command = code[--instruction];
								if (command == '[') {
									++depth;
								} else if (command == ']') {
									--depth;
								}
							} while (depth < 0);
						}
						break;
				}
			}
		} catch (IndexOutOfBoundsException e) {
			throw new BrainfuckException("Data pointer out of bounds");
		}
		return output.toString();
	}

	public static class BrainfuckException extends RuntimeException {
		private static final long serialVersionUID = -6576757866387410640L;

		public BrainfuckException(final String s) {
			super(s, null, false, false);
		}

		public BrainfuckException(final String s, final Throwable t) {
			super(s, t, false, false);
		}
	}
}
